﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NMF.Serialization;
using NMF.Serialization.Xmi;
using System.Text;
using NMF.Optimizations.Tests.BigPictureTests.My;
using NMF.Optimizations.Optimizer;
using NMF.Utilities;

using System.Linq;
using NMF.Tests;
using NMF.Models.Repository.Serialization;

namespace NMF.Optimizations.Tests.BigPictureTests
{
    [TestClass]
    public class TestClassDiagramRestructuring
    {
        private XmiSerializer serializer;

        [TestInitialize]
        public void InitializeSerializer()
        {
            var settings = new XmlSerializationSettings();
            settings.CaseSensitive = true;
            settings.Indent = true;
            settings.Encoding = Encoding.ASCII;
            settings.NameCase = XmlCaseType.AsInput;

            serializer = new ModelSerializer(settings);
            serializer.KnownTypes.Add(typeof(Model));
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase1_ImpactSensitive1()
        {
            RunTestcase1(new ImpactSensitiveOptimizer(), ClassDiagramRestructuringStrategy.SingleEntities);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase1_BruteForce1()
        {
            RunTestcase1(BruteForceOptimizer.Instance, ClassDiagramRestructuringStrategy.SingleEntities);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase2_ImpactSensitive1()
        {
            RunTestcase2(new ImpactSensitiveOptimizer(), ClassDiagramRestructuringStrategy.SingleEntities);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase2_BruteForce1()
        {
            RunTestcase2(BruteForceOptimizer.Instance, ClassDiagramRestructuringStrategy.SingleEntities);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase1_ImpactSensitive2()
        {
            RunTestcase1(new ImpactSensitiveOptimizer(), ClassDiagramRestructuringStrategy.EntityTuples);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase1_BruteForce2()
        {
            RunTestcase1(BruteForceOptimizer.Instance, ClassDiagramRestructuringStrategy.EntityTuples);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase2_ImpactSensitive2()
        {
            RunTestcase2(new ImpactSensitiveOptimizer(), ClassDiagramRestructuringStrategy.EntityTuples);
        }

        [TestMethod]
        public void Optimizations_ClassDiagramRestructuring_Testcase2_BruteForce2()
        {
            RunTestcase2(BruteForceOptimizer.Instance, ClassDiagramRestructuringStrategy.EntityTuples);
        }

        private void RunTestcase1(IOptimizer optimizer, ClassDiagramRestructuringStrategy strategy)
        {
            var model = serializer.Deserialize(@"data\testcase1.xmi") as Model;
            var restructuring = new ClassDiagramRestructuring(model, strategy);
            var evaluator = new ModelEvaluator(model);
            restructuring.Optimize(optimizer, evaluator);

            AssertCorrectTestcase1Output(model);
        }

        private void RunTestcase2(IOptimizer optimizer, ClassDiagramRestructuringStrategy strategy)
        {
            var model = serializer.Deserialize(@"data\testcase2.xmi") as Model;
            var restructuring = new ClassDiagramRestructuring(model, strategy);
            var evaluator = new ModelEvaluator(model);
            restructuring.Optimize(optimizer, evaluator);

            AssertCorrectTestcase2Output(model);
        }

        private void AssertCorrectTestcase1Output(Model model)
        {
            var a = model.Entitys.FirstOrDefault(e => e.Name == "A");
            var b = model.Entitys.FirstOrDefault(e => e.Name == "B");
            var c = model.Entitys.FirstOrDefault(e => e.Name == "C");
            var d = model.Entitys.FirstOrDefault(e => e.Name == "D");
            var s = model.Entitys.FirstOrDefault(e => e.Name == "S");

            a.AssertNotNull();
            b.AssertNotNull();
            c.AssertNotNull();
            d.AssertNotNull();
            s.AssertNotNull();

            Assert.AreEqual(2, s.Specialization.Count);
            AssertDerives(a, s);

            var newClass = s.Specialization.Select(g => g.Specific).FirstOrDefault(e => e != a);

            AssertSpecializations(newClass, b, c, d);

            AssertContainsProperty(a, "a");
            AssertContainsProperty(newClass, "b");
            AssertContainsProperty(b, "a");
        }

        private void AssertCorrectTestcase2Output(Model model)
        {
            var a = model.Entitys.FirstOrDefault(en => en.Name == "A");
            var b = model.Entitys.FirstOrDefault(en => en.Name == "B");
            var c = model.Entitys.FirstOrDefault(en => en.Name == "C");
            var d = model.Entitys.FirstOrDefault(en => en.Name == "D");
            var e = model.Entitys.FirstOrDefault(en => en.Name == "E");
            var f = model.Entitys.FirstOrDefault(en => en.Name == "F");
            var g = model.Entitys.FirstOrDefault(en => en.Name == "G");

            a.AssertNotNull();
            b.AssertNotNull();
            c.AssertNotNull();
            d.AssertNotNull();
            e.AssertNotNull();
            f.AssertNotNull();
            g.AssertNotNull();

            a.OwnedAttribute.AssertEmpty();
            b.OwnedAttribute.AssertEmpty();
            c.OwnedAttribute.AssertEmpty();
            d.OwnedAttribute.AssertEmpty();
            e.OwnedAttribute.AssertEmpty();
            f.OwnedAttribute.AssertEmpty();
            g.OwnedAttribute.AssertEmpty();

            var newClass1 = a.Generalization.First().General;
            var newClass2 = g.Generalization.First().General;

            newClass1.AssertNotNull();
            newClass2.AssertNotNull();

            AssertSpecializations(newClass2, newClass1, g);
            AssertSpecializations(newClass1, a, d);
            AssertSpecializations(a, b, c);
            AssertSpecializations(d, e, f);

            AssertContainsProperty(newClass1, "a");
            AssertContainsProperty(newClass2, "b");
        }

        private void AssertDerives(Entity specific, Entity general)
        {
            Assert.IsTrue(general.Specialization.Select(g => g.Specific).Contains(specific));
        }

        private void AssertSpecializations(Entity baseEntity, params Entity[] specializations)
        {
            baseEntity.Specialization.Select(g => g.Specific).AssertContainsOnly(specializations);
        }

        private void AssertContainsProperty(Entity entity, string propertyName)
        {
            Assert.IsTrue(entity.OwnedAttribute.Any(p => p.Name == propertyName));
        }
    }
}
